<html>
<head>
<style type="text/css">

tr { height: 4em }
th { width: 5.5% }
th#first { width: 12% }
table, th, td {
	font-family: "courier", monospace;
	text-align: center;
	border-width: 1px;
	border-collapse: collapse;
	border-color: gray;
	border-style: solid;
	padding: 4px;
}

.values {
	font-weight: normal;
	font-size: small;
	white-space: nowrap;
}

.label {
	vertical-align: bottom;
	white-space: pre;
}

select {
	margin-left: auto;
	margin-right: auto;
	color: #FFF;
	background: #222;
	display: block;
	border: 0px;
}

select[disabled] { color: #AAA }


.false { background-color: #FBA; cursor: pointer }
.true  { background-color: #BFC; cursor: pointer }
.constant { background-color: #BBB }
.chosen { text-decoration: underline }
.hilited { background-color: #EE9; cursor: pointer }
.unhilited { cursor: pointer }
.bar { text-decoration: overline }

</style>
<script type="text/javascript">

var mode = "user";

var types = {
	"binary": ["0", "1"],
	"reg": ["r0", "r1", "r2", "r3", "r4", "r5", "PC", "UPC"],
	"ops": ["<", "&", "|", "+"],
	"flags": ["f", "0", "c", "!c", "Q4", "Q5", "Q6", "Q7"],
	"carry": ["1", "F"], 
	"x": ["0", "a"],
	"y": ["a", "a>", "b", "!b"],
};

var pulldowns = {
	"A": ["binop", "unop", "illop"],
	"B": ["binop", "illop"],
	"W": ["binop"],
	"C": ["binop", "unop"],
	"X": ["binop", "unop"],
	"Y": ["binop"],
	"F": ["binop"],
	"OP": ["binop"],
}

function load() {
	var selects = document.getElementsByTagName("select");
	for(var i = 0; i < selects.length; i++) {
		var select = selects.item(i);
		var options = types[select.className];
		select.onchange = updateEncoding;
		for(var j = 0; j < options.length; j++) {
			var option = options[j];
			var child = document.createElement("option");
			child.text = option;
			child.value = j;
			select.appendChild(child);
		}
	}
	var sliders = document.getElementsByName("slider");
	for(var i  = 0; i < sliders.length; i++) {
		var slider = sliders.item(i);
		var bits = slider.getAttribute("bits");

		var input = document.createElement("input");
		input.type = "range";
		input.value = 0;
		switch(slider.className) {
			case "signed":
				var min = -Math.pow(2,bits-1);
				var max = Math.pow(2,bits-1)-1;
				break
			case "unsigned":
				var min = 0;
				var max = Math.pow(2,bits)-1;
				break
		}
		input.setAttribute("min", min);
		input.setAttribute("max", max);
		input.setAttribute("step", 1);
		input.id = "slider" + slider.id;
		pulldowns[input.id] = slider.getAttribute("row").split(",");

		var label = document.createElement("span");
		var readout = document.createTextNode(input.value);
		label.className = "label";

		input.label = readout;
		input.oninput = updateLabelAndEncoding;
		input.chars = Math.max(("" + max).length, ("" + min).length);
		input.oninput();
	
		label.appendChild(readout);
		slider.appendChild(input);
		slider.appendChild(label);
	}

	var initial = Math.round(65536*Math.random());
	reEncode(decToHex(initial));
}

function updateLabel(slider) {
	var str = slider.value;
	while(str.length < slider.chars) str = " " + str;
	slider.label.data = str;
}

function updateLabelAndEncoding() {
	updateLabel(this);
	updateEncoding();
}

var hilitedType;
function hiliteType(id) {
	var th = document.getElementById(id);
	if(!th) return;
	if(hilitedType) hilitedType.className = "unhilited";
	(hilitedType = th).className = "hilited";
	for(var a in pulldowns) {
		var match = 0;
		var types = pulldowns[a];
		for(var i = 0; i < types.length; i++) {
			if(types[i] == id) {
				match = 1;
				break;
			}
		}
		document.getElementById(a).disabled = !match;
	}
}

function getSelect(name) {
	return parseInt(document.getElementById(name).value);
}

function setSelect(name, value) {
	document.getElementById(name).value = value;
}

function doSelect(name, value) {
	if(null != value) {
		setSelect(name, value);
		return value;
	} else {
		return getSelect(name);
	}
}

function getSlider(name) {
	var slider = document.getElementById(name).firstChild;
	var value = parseInt(slider.value);
	if(value < 0) value += 2*slider.getAttribute("max")+2;
	return value;
}

function setSlider(name, value) {
	var div = document.getElementById(name);
	var slider = div.firstChild;
	if(div.getAttribute("class") == "signed") {
		var max = slider.getAttribute("max");
		value = unsignedToSigned(value, max);
	}
	//toggle type for safari bug
	slider.type = "text";
	slider.value = value;
	slider.type = "range";
	updateLabel(slider);
}

function doSlider(name, value) {
	if(null != value) {
		setSlider(name, value);
		return value;
	} else {
		return getSlider(name);
	}
}

function unsignedToSigned(value, max) {
	if(value > max) return value - 2 * max - 2;
	else return value;
}

function encode(bits, vals) {
	if(bits.length != vals.length) return;
	var bitstring = "";
	var offset = 16;
	for(var i = 0; i < bits.length; i++) {
		var str = vals[i].toString(2);
		while(str.length < bits[i]) str = "0" + str;
		bitstring += str;
		for(var j = 0; j < bits[i]; j++) {
			var which = "b" + (offset - j - 1);
			var value = 0 != str.charAt(j) ? "true" : "false";
			document.getElementById(which).className = value;
		}
		offset -= bits[i];
	}
	var hexstring = parseInt(bitstring, 2).toString(16);
	while(hexstring.length < 4) hexstring = "0" + hexstring;
	setEncoding(hexstring);
}

var lastEncodingString;
function setEncoding(str) {
	var ret = true;
	if(str == lastEncodingString) return false;
	while(str.length < 4) str = "0" + str;
	if(str.length > 4) str = str.substring(str.length - 4);
	if(!str.match(/^[0-9A-Fa-f]{4}$/)) {
		str = lastEncodingString;
		ret = false;
	}

	lastEncodingString = document.getElementById("encoding").value = str;
	return ret;
}

var lastEncoding;
function updateEncoding() {
	if(lastEncoding) lastEncoding();
}

var scanStart;
function nextEncoding() {
	var hex = incEncoding(1);
	if(hex == scanStart) scanEncodings(document.getElementById("scan"));
}

function reEncode(hex) {
	setEncoding(hex);
	var binstr = hexToBin(hex);
	decode(binstr);
}

var interval;
function scanEncodings(button) {
	if(interval != null) {
		clearInterval(interval);
		interval = null;
		button.value = "scan";
	} else {
		scanStart = decToHex(parseInt(lastEncodingString, 16));
		interval = setInterval(nextEncoding, 10);
		button.value = "stop";
	}
}

function incEncoding(inc) {
	var hex = decToHex(inc + parseInt(lastEncodingString, 16));
	reEncode(hex);	
	return hex;
}

function binop(a, b, w, c, f, o, y, x) {
	lastEncoding = binop;
	hiliteType("binop");

	a = doSelect("A", a);
	b = doSelect("B", b);
	w = doSelect("W", w);
	c = doSelect("C", c);
	x = doSelect("X", x);
	y = doSelect("Y", y);
	f = doSelect("F", f);
	o = doSelect("OP", o);

	var bits = [3, 3, 1, 1, 1, 2, 3, 2];
	var vals = [a, b, w, c, x, y, f, o];
	encode(bits, vals);

	var abbr = Array();
	abbr[ "a<a"  ] = "nop";
	abbr[ "0<a"  ] = "clr";
	abbr[ "a&b"  ] = "and";
	abbr[ "a&!b" ] = "and!";
	abbr[ "a|b"  ] = "or";
	abbr[ "a|!b" ] = "or!";
	abbr[ "0|a>" ] = "shr";
	abbr[ "0|b"  ] = "mov";
	abbr[ "0|!b" ] = "mov!";
	abbr[ "a+a"  ] = "shl+1";
	abbr[ "a+b"  ] = "add1";
	abbr[ "a+!b" ] = "sub";
	abbr[ "0+a"  ] = "inc1";
	abbr[ "0+a>" ] = "shr+1";
	abbr[ "0+b"  ] = "mov+1";
	abbr[ "0+!b" ] = "mov-";
	abbr[ "a+a,f"  ] = "shl+f";
	abbr[ "a+b,f"  ] = "addc";
	abbr[ "a+!b,f" ] = "subb";
	abbr[ "0+a,f"  ] = "inc1?";
	abbr[ "0+a>,f" ] = "shr+f";
	abbr[ "0+b,f"  ] = "mov+f";

	var mnemonic = types.x[x] + types.ops[o] + types.y[y];
	if(c) mnemonic += ",f";
	if(abbr[mnemonic]) mnemonic = abbr[mnemonic];
	if(f) mnemonic += "." + types.flags[f];

	var meaning = "";
	if(w) meaning += "@";
	meaning += mnemonic;

	meaning += " " + types.reg[a];
	meaning += " " + (a == b ? "@" : types.reg[b]);

	describeMeaning(meaning);
}

function unop(a, c, i, x) {
	lastEncoding = unop;
	hiliteType("unop");

	a = doSelect("A", a);
	c = doSelect("C", c);
	x = doSelect("X", x);
	i = doSlider("I", i);

	var bits = [3, 3, 1, 1, 1, 7];
	var vals = [a, a, 1, c, x, i];
	encode(bits, vals);

	var astr = types.reg[a];
	var immstr = 1 + unsignedToSigned(i, 63);
	var cond = c == 1 ? "?" : "";

	var str = "unknown";
	if(x == 0) {
		str = "set" + cond + " " + astr + ", " + immstr;
	} else {
		if(immstr == 0) {
			str = "nop";
		} else if(immstr < 0) {
			str = "dec" + cond + " " + astr + ", " + (-immstr);
		} else {
			str = "inc" + cond + " " + astr + ", " + immstr;
		}
	}	
		
	describeMeaning(str);

}

function intop(sw) {
	lastEncoding = intop;
	hiliteType("intop");

	sw = doSlider("SW", sw);

	var bits = [3, 3, 10];
	var vals = [7, 7, sw];
	encode(bits, vals);

	describeMeaning("sys " + sw);
}

function illop(a, b, sw) {
	lastEncoding = illop;
	hiliteType("illop");

	a = doSelect("A", a);
	b = doSelect("B", b);
	sw = doSlider("SW", sw);

	var bits = [3, 3, 10];
	var vals = [a, b, sw];
	encode(bits, vals);

	describeMeaning("undefined");
}

function decode(binstr) {
	var a =	parseInt(binstr.substring( 0,  3), 2);
	var b =	parseInt(binstr.substring( 3,  6), 2);
	var w =	parseInt(binstr.substring( 6,  7), 2);
	var c =	parseInt(binstr.substring( 7,  8), 2);
	var x =	parseInt(binstr.substring( 8,  9), 2);
	var y =	parseInt(binstr.substring( 9, 11), 2);
	var f =	parseInt(binstr.substring(11, 14), 2);
	var o =	parseInt(binstr.substring(14, 16), 2);
	var i =	parseInt(binstr.substring( 9, 16), 2);
	var s =	parseInt(binstr.substring( 6, 16), 2);

	if(a == 7 && b == 7 && mode == "user") {
		intop(s);
	} else if((a == 7 || b == 7) && mode == "user") {
		illop(a, b, s);
	} else if(w == 1 && a == b) {
		unop(a, c, i, x);
	} else {
		binop(a, b, w, c, f, o, y, x);
	}
}

function describeMeaning(str) {
	var div = document.getElementById("specificForm");
	div.lastChild.data = str;
}

function decToHex(val) {
	var hex = val.toString(16);
	while(hex.length > 4) hex = hex.substring(1);
	return hex;
}

function hexToBin(str) {
	var binstr = parseInt(str, 16).toString(2);
	while(binstr.length < 16) binstr = "0" + binstr;
	return binstr;
}

function textChanged(field) {
	var val = field.value;
	if(!setEncoding(val)) return;
	var binstr = hexToBin(field.value);
	decode(binstr);
}

function toggleBit(bit) {
	var binstr = hexToBin(lastEncodingString);
	if(bit != null) {
		var set = "true" == document.getElementById("b" + bit).className ? 0 : 1;
		binstr = binstr.substring(0, 15-bit) + set + binstr.substring(16-bit);
	}
	decode(binstr);
}

function setMode(span) {
	var which = span.id;
	if(mode == which) return;
	span.className = "chosen";
	document.getElementById(mode).className = "";
	mode = which;
	toggleBit(null);
}

function instructions() {
	alert(
		"To encode a specific instruction,\n" +
		"click on the appropriate instruction type,\n" +
		"and adjust pull-downs and sliders to suit.\n" +
		"\n" +
		"To decode an instruction,\n" +
		"enter its hex value in the text box,\n" +
		"or click on bit numbers to toggle.");
}	

</script>
</head>
<body onload="load()">

<!--

Need to figure out actual flag choices.
	Need to be able to not set it - that is, set to previous value
		Must have for addop so kernel can save user state fully
		Should have for binop to make things simpler
	Need to be able to clear the flag
		This must be done cheaply before most addops
		So addop must be able to clear the flag for next addop
		binops can do this by having Cin = 0 with the equal carry chain
	For most flags, want to have both normal and inverted saving possible
		But this is not needed for flag clearing, no need to force 1
		Unless that makes things simpler, of course
	The equal flag is really nice, but can't be used with addop
		That portion of the carry chain is already in use
		But we can have a separate 16-and... not too expensive
	Overflow / carry / negative
		Overflow may be only interesting to add
		Is carry really interesting at all?
	Greater than / less than

	F=0		no change		- both, must be encoded as zero for magic instruction
	F=1		a:zero, l:equal
	F=2		a:over, l:\equal
	F=3		negative
	F=4		\negative
	F=5		less than
	F=6		greater or equal
	F=7		greater than
	F=8		less or equal

			zero		- addop only, binop can use equal
			equal		- binop, inverted supported
Next steps:
	Figure out what the "standard flags" mean (and how they're defined)
	for the cmp instruction, for all 8 X/Y input combinations.
	Decide which of these are interesting.	
	See if there's any difference in what we want for addop

Need to define instruction mnemonics.

Print warning if the encoding is invalid (due to modes)
Word size is 16 bits, with no byte addressability
	ops c=1 s=1 i=1 l=2 h=4 f=2 d=4 x=4 p=1
One ABI option:
	Have the stack grow up
	Push arguments on the stack from right to left
		But not the first two arguments!
	Push pc register onto stack
		So stack points to first unused location
	r0   - volatile / 16-bit ret value / low of 32-bit ret value
	r1   - volatile / 16-bit arg0 / high of 32-bit arg0 / high of 32-bit ret value
	r2   - volatile / 16-bit arg1 / low of 32-bit arg0
	r3:4 - nonvolatile
	r5   - stack pointer (grows down)
	r6   - user program counter   (+1)
	r7   - kernel program counter (+1)
	flag - volatile

#load a constant
0	0c94	mov r0, pc ; add r0, 1
1	6c90	add pc, 1
2	xxxx	constant
3	0e14	mov r0, (r0)

#call a function with up to two args in r1, r2
0	dc90	push sp, pc					#put 1 (.) onto stack, increment sp
1	6e15	add  pc, (pc)				#do a relative jump by...
2	xxxx	constant					#target address - 2 (.)
3	....								#return here, no unwind needed

#call a function with arg0 in r0 ... arg3 in r3
0	d690	push sp, r3					#put arg3 onto the stack first
1	d490	push sp, r2					#next do arg2
2	2214	mov  r2, r1					#arg1 goes into r2 only
3	1014	mov  r1, r0					#arg0 goes in r1
4	dc90	push sp, pc					#put 5 (.) onto stack, increment sp
5	6e15	add  pc, (pc)				#do a relative jump by
6	xxxx	constant					#target address - 6 (.)...
7	defd	sub  sp, 2					#take off two args we added

#function prologue [example, use as needed]
2	d690	push sp, r3					#if needed, save the non-volatile
3	d890	push sp, r4					#registers so we can restore them
0	d490	push sp, r2					#if needed, save arg1
1	d290	push sp, r1					#if needed, save arg0
4	de89	add  sp, 4					#reserve space for 4 locals

#function epilogue [N locals and saved registers]
0	defe	sub  sp, 1[+N]				#point to the return address
1	3a94	mov  r3, sp ; add r3, 1		#if needed, get pointer to saved r3
2	4694	mov  r4, r3 ; add r4, 1		#if needed, get pointer to saved r4
3	3e14	mov  r3, (r3)				#if needed, restore r3
4	4e14	mov  r4, (r4)				#if needed, restore r4
5	2a14	mov  r2, sp					#put link pointer in r1
6	2e94	mov  r2, (r2) ; add r2, 1	#fetch (sp)+1
7	6494	mov  pc, r2 ; add pc, 1		#return to (sp)+2

#kernel mode fibonacci sequence
0	1d01	mov r0, 0					#first number in r0
1	3d05	mov r1, 1					#second number in r1
2	9d0d	mov r4, 3					#put loop start in r4
			loop:
3	4408	mov r2, r1					#copy previous number
4	400a	add r2, r0					#and calculate the next
5	0408	mov r0, r1					#move previous down a slot
6	2808	mov r1, r2					#move current down a slot
7	f008	mov kpc, r4					#loop

-->

<table>
<caption>
Instruction Encoder / Decoder <a href="#" onclick="instructions()">[?]</a>
</caption>

<thead>
<tr>
<th id="first" valign="bottom">
<span onclick="incEncoding(-1)">-</span>
<input type="text" size="5" maxlength="5" id="encoding" onkeyup="textChanged(this)"/>
<span onclick="incEncoding(1)">+</span>
<div class="values">
<span id="user" onclick="setMode(this)" class="chosen">User</span> /
<span id="kernel" onclick="setMode(this)">Kernel</span>
</div>
</th>
<th id="b15" onclick="toggleBit(15)">F</th>
<th id="b14" onclick="toggleBit(14)">E</th>
<th id="b13" onclick="toggleBit(13)">D</th>
<th id="b12" onclick="toggleBit(12)">C</th>
<th id="b11" onclick="toggleBit(11)">B</th>
<th id="b10" onclick="toggleBit(10)">A</th>
<th id="b9"  onclick="toggleBit( 9)">9</th>
<th id="b8"  onclick="toggleBit( 8)">8</th>
<th id="b7"  onclick="toggleBit( 7)">7</th>
<th id="b6"  onclick="toggleBit( 6)">6</th>
<th id="b5"  onclick="toggleBit( 5)">5</th>
<th id="b4"  onclick="toggleBit( 4)">4</th>
<th id="b3"  onclick="toggleBit( 3)">3</th>
<th id="b2"  onclick="toggleBit( 2)">2</th>
<th id="b1"  onclick="toggleBit( 1)">1</th>
<th id="b0"  onclick="toggleBit( 0)">0</th>
</tr>
</thead>
<tbody>

<tr>
<th onclick="intop()" id="intop" class="unhilited">INT</th>
<td colspan="3" class="constant"><b>7</b><div class="values">A</div></td>
<td colspan="3" class="constant"><b>7</b><div class="values">B</div></td>
<td colspan="10" rowspan="2"><b>SYSCALL (IGNORED)</b><div name="slider" id="SW" class="unsigned" bits="10" row="intop,illop"/></td>
</tr>

<tr>
<th onclick="illop()" id="illop" class="unhilited">UNDEF</th>
<td colspan="3" rowspan="3"><b>A</b><select class="reg" id="A"/></td>
<td colspan="3" rowspan="2"><b>B</b><select class="reg" id="B"/></td>
</tr>

<tr>
<th onclick="binop()" id="binop" class="unhilited">BINOP</th>
<td colspan="1" rowspan="1"><b>W</b><select class="binary" id="W"/></td>
<td colspan="1" rowspan="2"><b>C</b><select class="carry" id="C"/></td>
<td colspan="1" rowspan="2"><b>X</b><select class="x" id="X"/></td>
<td colspan="2" rowspan="1"><b>Y</b><select class="y" id="Y"/></td>
<td colspan="3"><b>F</b><select class="flags" id="F"/></td>
<td colspan="2"><b>OP</b><select class="ops" id="OP"/></td>
</tr>

<tr>
<th onclick="unop()" id="unop" class="unhilited">UNOP</th>
<td colspan="3" class="constant"><b>A</b><div class="values">B</div></td>
<td colspan="1" class="constant"><b>1</b><div class="values">W</div></td>
<td colspan="7"><b>I</b><div name="slider" id="I" class="signed" bits="7" row="unop"/></td>
</tr>

</tbody>
<tfoot>
<tr>
<td><input type=submit id="scan" value="scan" onclick="scanEncodings(this)"/></td>
<td colspan="16">
<div id="specificForm">specific form</div>
</td>
</tr>
</tfoot>
</table>

</body>
</html>
